/* 
 * Copyright (c) 2016 Qualcomm Technologies, Inc. 
 * All Rights Reserved. 
 * Qualcomm Technologies, Inc. Confidential and Proprietary. 
 */
package com.thunder.rebot.rebotclient.vam;

import android.graphics.PointF;
import android.graphics.RectF;
import android.util.Log;

import com.thunder.rebot.rebotclient.vam.entity.FaceFrame;
import com.thunder.rebot.rebotclient.vam.utils.Utils;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * Utility class for algorithms
 */
public class Algo {
// ==========================================================================================
    private static final String TAG_FACE_TRACK = "FACE_TRACK";
// ==========================================================================================
    public static final float MIN_OVERLAP_RATIO = 0.50f;
    private static final float KEEP_OVERLAP_RATIO = 0.80f;
    private static final float MIN_SIZE_RATIO = 0.60f;

    /**
     * interface for face matching
     */
    public interface FaceMatcher {
        /**
         * Track face algorithm
         *
         * @param frame1 old face frame
         * @param frame2 new face frame
         * @return true if frame gets matched, false otherwise
         */
        boolean match(FaceFrame frame1, FaceFrame frame2);
    }

    /**
     * gets the overlapping frame index
     *
     * @param frame         face frame
     * @param newFaceFrames new face frames
     * @return index of the matched/overlapped rect
     */
    public static int getOverlappingRect(FaceFrame frame, ArrayList<FaceFrame> newFaceFrames) {
        if (frame == null) {
            return -1;
        }
        for (int i = 0; i < newFaceFrames.size(); i++) {
            FaceFrame newFaceFrame = newFaceFrames.get(i);
            RectF newRect = newFaceFrame.getRect();
            float overlapping = Algo.getOverlapping(frame.getRect(), newRect);
            if (overlapping > MIN_OVERLAP_RATIO) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Method to update the presence of face frame
     *
     * @param oldFrame old frame
     * @param newFrame new frame
     * @return true if the frame location is cahanged, false otherwise
     */
    public static boolean updatePresence(FaceFrame oldFrame, FaceFrame newFrame) {
        boolean changed = false;
        oldFrame.notifyThere();
        //Log.d(TAG_FACE_TRACK, "old face update: " + newRect + "," + oldFrame + ", ov: "
        //        + overlapping);
        if (true) {
            oldFrame.setRect(newFrame.getRect());
            Log.d(TAG_FACE_TRACK, "updated old face: " + oldFrame);
            return true;
        }

        float overlapping = Algo.getOverlapping(oldFrame.getRect(), newFrame.getRect());
        if (overlapping < KEEP_OVERLAP_RATIO) {
            oldFrame.setRect(newFrame.getRect());
            changed = true;
            Log.d(TAG_FACE_TRACK, "updated old face: " + oldFrame);

        } else {
            float widthRatio = newFrame.getRect().width() / oldFrame.getRect().width();
            //Log.d(TAG_FACE_TRACK, "width ratio: " + widthRatio);
            if (widthRatio < MIN_SIZE_RATIO) {
                oldFrame.setRect(newFrame.getRect());
                changed = true;
            }
        }
        return changed;
    }

    /**
     * Get the overlapping area in percentage
     *
     * @param rect1 existing rectangle one
     * @param rect2 new rect on top of rect1
     * @return overlapping percentage
     */
    public static float getOverlapping(RectF rect1, RectF rect2) {
        RectF tallerRectF = rect1;
        RectF smallerRectF = rect2;
        if (rect1.height() < rect2.height()) {
            tallerRectF = rect2;
            smallerRectF = rect1;
        }

        float startY = Math.max(tallerRectF.top, smallerRectF.top);
        float endY = Math.min(tallerRectF.bottom, smallerRectF.bottom);
        float overlappedY = endY - startY;
        if (overlappedY < 0) {
            return 0;
        }

        float startX = Math.max(tallerRectF.left, smallerRectF.left);
        float endX = Math.min(tallerRectF.right, smallerRectF.right);
        float overlappedX = endX - startX;
        if (overlappedX < 0) {
            return 0;
        }

        return (overlappedX * overlappedY) / (rect2.width() * rect2.height());
    }

    /**
     * Method to track the face in new frames
     *
     * @param oldFrame      oldframe whihc needs to be looked into
     * @param newFaceFrames list of new faces
     * @param matcher       matcher
     * @return true if frame location is updated, false otherwise
     */
    public static boolean trackFace(FaceFrame oldFrame, ArrayList<FaceFrame> newFaceFrames, FaceMatcher matcher) {
        //Log.d(TAG_FACE_TRACK, "Got new faces " + newRects.size());
        boolean changed = false;
        for (FaceFrame newFaceFrame : newFaceFrames) {
            if (matcher.match(oldFrame, newFaceFrame)) {
                return Algo.updatePresence(oldFrame, newFaceFrame);
            }
        }
        oldFrame.NotifyNotThere();
        return false;
    }

    /**
     * Method to track the new faces with older faces
     *
     * @param frames        older faces
     * @param newFaceFrames new faces
     * @param matcher       matching algo
     * @return true if location is changed, false otherwise
     */
    public static boolean trackFaceFrames(ArrayList<FaceFrame> frames, ArrayList<FaceFrame> newFaceFrames, FaceMatcher matcher) {
        //Log.d(TAG_FACE_TRACK, "Got new faces " + newRects.size());
        boolean changed = false;
        int startIndex = 0;
        for (FaceFrame newFaceFrame : newFaceFrames) {
            RectF newRect = newFaceFrame.getRect();
            int matchedAt = -1;
            for (int oldIndex = startIndex; oldIndex < frames.size(); oldIndex++) {
                FaceFrame oldFrame = frames.get(oldIndex);
                if (matcher.match(oldFrame, newFaceFrame)) {
                    matchedAt = oldIndex;
                    break;
                }
            }
            startIndex++;
            if (matchedAt == -1) { // new face
                FaceFrame newFrame = new FaceFrame(newFaceFrame.getId(), newRect);
                frames.add(0, newFrame);
                changed = true;
                Log.d(TAG_FACE_TRACK, "New face " + newRect + "=>" + newFrame);

            } else { // older face
                //加判断避免indexoutof
                if(matchedAt<=frames.size()-1){
                    FaceFrame oldFrame = frames.remove(matchedAt);
                    changed = Algo.updatePresence(oldFrame, newFaceFrame);
                    frames.add(0, oldFrame);
                }
            }
        }
        for (int remaining = startIndex; remaining < frames.size(); remaining++) {
            FaceFrame remainingFrame = frames.get(remaining);
            remainingFrame.NotifyNotThere();
        }
        Iterator<FaceFrame> itr = frames.iterator();
        while (itr.hasNext()) {
            FaceFrame frame = itr.next();
            if (frame.getAbsence() > Utils.MAX_ABSENCE) {
                Log.d(TAG_FACE_TRACK, "Removing " + frame);
                itr.remove();
                changed = true;
            }
        }
        return changed;
    }
// ==========================================================================================
    private static final String TAG_ZONES_ALGO = "ZONES_ALGO";
// ==========================================================================================

    /**
     * Checks if the quadrilateral is valid or not
     *
     * @param pts quadrilateral points
     * @return true if valid false otherwise
     */
    public static boolean isQuadValid(PointF[] pts) {
        int lines = pts.length;
        for (int line1 = 0; line1 < lines - 2; line1++) {
            int line2 = line1 + 2;
            for (int i = 0; i < lines - 3; i++) {
                if (Algo.areLinesIntersecting(pts, line1, line2)) {
                    return false;
                }
                line2++;
                if (line2 == lines) {
                    line2 = 0;
                }
            }
        }
        return true;
    }

    private static boolean areLinesIntersecting(PointF[] pts, int index1, int index2) {
        PointF line1Pt1 = pts[index1];
        int line1Pt2Idx = index1 + 1;
        if (line1Pt2Idx == pts.length) {
            line1Pt2Idx = 0;
        }
        PointF line1Pt2 = pts[line1Pt2Idx];

        PointF line2Pt1 = pts[index2];
        int line2Pt2Idx = index2 + 1;
        if (line2Pt2Idx == pts.length) {
            line2Pt2Idx = 0;
        }
        PointF line2Pt2 = pts[line2Pt2Idx];
        Log.d(TAG_ZONES_ALGO, "Line " + index1 + "=>" + line1Pt1 + "," + line1Pt2);
        Log.d(TAG_ZONES_ALGO, "Line " + index2 + "=>" + line2Pt1 + "," + line2Pt2);
        if (line1Pt2.x == line1Pt1.x && line2Pt2.x == line2Pt1.x) {
            Log.d(TAG_ZONES_ALGO, "Parallel vertical lines");
            return false;
        }
        float xIntersect = 0;
        if (line1Pt2.x == line1Pt1.x) {
            Log.d(TAG_ZONES_ALGO, "vertical line: " + index1);
            xIntersect = line1Pt1.x;

        } else if (line2Pt2.x == line2Pt1.x) {
            xIntersect = line2Pt1.x;
            Log.d(TAG_ZONES_ALGO, "vertical line: " + index2);

        } else {
            // m = (y2 - y1)/(x2 - x1)
            float slope1 = (line1Pt2.y - line1Pt1.y) / (line1Pt2.x - line1Pt1.x);
            float slope2 = (line2Pt2.y - line2Pt1.y) / (line2Pt2.x - line2Pt1.x);
            if (slope1 == slope2) {
                Log.d(TAG_ZONES_ALGO, "Parallel lines " + slope2);
                return false;
            }
            Log.d(TAG_ZONES_ALGO, "slopes " + slope1 + "," + slope2);
            xIntersect = (slope1 * line1Pt1.x - slope2 * line2Pt1.x - line1Pt1.y + line2Pt1.y)
                    / (slope1 - slope2);
        }
        float startX = Math.min(line1Pt1.x, line1Pt2.x);
        float endX = Math.max(line1Pt1.x, line1Pt2.x);
        if (xIntersect < startX || xIntersect > endX) {
            Log.d(TAG_ZONES_ALGO, "line x outside1 [" + xIntersect + "] => " + startX + "," + endX);
            return false;
        }
        Log.d(TAG_ZONES_ALGO, "line x inside 1 [" + xIntersect + "] => " + startX + "," + endX);

        startX = Math.min(line2Pt1.x, line2Pt2.x);
        endX = Math.max(line2Pt1.x, line2Pt2.x);
        if (xIntersect < startX || xIntersect > endX) {
            Log.d(TAG_ZONES_ALGO, "line x outside 2 [" + xIntersect + "] => " + startX + "," + endX);
            return false;
        }

        Log.d(TAG_ZONES_ALGO, "line x inside 2 [" + xIntersect + "] => " + startX + "," + endX);
        return true;
    }

    /**
     * Check if the x,y point is inside rectangle or not
     *
     * @param rect   rectangle
     * @param x      point x value
     * @param y      point y value
     * @param offset offset to be added on both sides of the rectangle
     * @return true if point is in rectangle, false otherwise
     */
    public static boolean isPointInside(RectF rect, float x, float y, float offset) {
        return (x > (rect.left - offset) && x < (rect.right + offset) && y > (rect.top - offset) && y < (rect.bottom + offset)) ? true
                : false;
    }
}
